package algorithm;

import datastructure.Fraction;
import datastructure.Pair;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import utility.FileReader;
import utility.InstancesConverter;
import weka.classifiers.Classifier;
import weka.classifiers.Evaluation;
import weka.classifiers.bayes.NaiveBayes;
import weka.core.Instance;
import weka.core.Instances;

public class NaiveBayesianModel {

  private ArrayList<HashMap<String, Pair<Fraction, Fraction>>> data;
  private Pair<Fraction, Fraction> probability;
  private NaiveBayes model;
  
  public NaiveBayesianModel() {
  }

  public NaiveBayesianModel(NaiveBayes model) {
    this.model = model;
  }

  public NaiveBayesianModel(ArrayList<HashMap<String, Pair<Fraction, Fraction>>> data, Pair<Fraction, Fraction> probability) {
    this.data = data;
    this.probability = probability;

  }

  public String executeInstance(ArrayList<String> instance) {
    double right = probability.first.getDouble(), wrong = probability.second.getDouble();
    for (int attribute_number = 0; attribute_number < instance.size(); attribute_number++) {
      right *= data.get(attribute_number).get(instance.get(attribute_number)).first.getDouble();
      wrong *= data.get(attribute_number).get(instance.get(attribute_number)).second.getDouble();
    }
    return (right >= wrong) ? "1" : "0";
  }

  public String executeInstance(Instance inc) {
    ArrayList<String> instance = InstancesConverter.convert2(inc);
    double right = probability.first.getDouble(), wrong = probability.second.getDouble();
    for (int attribute_number = 0; attribute_number < instance.size(); attribute_number++) {
      right *= data.get(attribute_number).get(instance.get(attribute_number)).first.getDouble();
      wrong *= data.get(attribute_number).get(instance.get(attribute_number)).second.getDouble();
    }
    return (right >= wrong) ? "1" : "0";
  }
  
  public String executeInstanceWEKA(Instance instance) {
    NaiveBayes nb = new NaiveBayes();
    try {
      return Double.toString(model.classifyInstance(instance));
    } catch (Exception e) {
      System.out.println(e);
      return "";
    }
  }

  public void saveModel(String path) throws IOException {
    Object[] test = new Object[2];
    test[0] = data;
    test[1] = probability;
    ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream(path));
    oos.writeObject(test);
    oos.flush();
    oos.close();
  }

  public void loadModel(String path) throws IOException, ClassNotFoundException {
    ObjectInputStream ois = new ObjectInputStream(
            new FileInputStream(path));
    Object[] test = (Object[]) ois.readObject();
    data = (ArrayList<HashMap<String, Pair<Fraction, Fraction>>>)test[0];
    probability = (Pair<Fraction, Fraction>)test[1];
    ois.close();
  }

  public void saveModelWEKA(String path) throws Exception {
    weka.core.SerializationHelper.write(path, model);
  }

  public void loadModelWEKA(String path) throws Exception {
    model = (NaiveBayes) weka.core.SerializationHelper.read(path);
  }
  
  @Override
  public String toString() {
    return probability.first.toString() + " " + probability.second.toString();
  }




    public static void prin(int[] a) {
        int i = 0;
        while (i < a.length) {
            System.out.print(a[i]);
            System.out.print(" ,");
            i++;
        }
        System.out.println("");
    }
    public String crossv(int fold, Instances dat) {
        Classifier cls = new NaiveBayes();
        String hasil = "cross validation failed";
        try {
            cls.buildClassifier(dat);
            Evaluation eval = new Evaluation(dat);
            eval.crossValidateModel(cls, dat, fold, new Random(1));
            hasil = (eval.toSummaryString("\nResults\n======\n", false));
        } catch (Exception ex) {
            Logger.getLogger(NaiveBayesianModel.class.getName()).log(Level.SEVERE, null, ex);
        }
        return hasil;
    }
    
    public String crossval(int fold, Instances dat) {
        NaiveBayesian asd = new NaiveBayesian();
        int i = 0;
        int j = 0;
        int k = 0;
        int bagi = (dat.numInstances() / fold);
        int[] hasil = new int[fold];


        String asss = "kosong.arff";
        Instances qwe = FileReader.readFile(asss);

        while (i < fold) {
            Instances tests = new Instances(qwe);
            tests.setClassIndex(0);
            int z = i * (dat.numInstances() / fold);
            while (z < ((i + 1) * bagi)) {
                Instance piece = dat.instance(z);
                tests.add(piece);
                z++;
            }
            NaiveBayesianModel model = asd.generateModel(tests);
            //mbuat model dati tests

            if (i == 0) {
                j = bagi;
                while (j < dat.numInstances()) {
                    Instance train = dat.instance(j);
                    String harus = train.toString();
                    if (!model.executeInstance(InstancesConverter.convert2(train)).equals(harus.substring(0, 1))) {
                        hasil[i] = hasil[i] + 1;
                    }
                    j++;
                }
            } else if (i == (fold - 1)) {
                j = 0;

                while (j < ((fold - 1) * bagi)) {
                    Instance train = dat.instance(j);
                    String harus = train.toString();
                    if (!model.executeInstance(InstancesConverter.convert2(train)).equals(harus.substring(0, 1))) {
                        hasil[i] = hasil[i] + 1;
                    }
                    j++;
                }

            } else {
                j = 0;
                while (j < (i * bagi)) {
                    Instance train = dat.instance(j);
                    String harus = train.toString();
                    if (!model.executeInstance(InstancesConverter.convert2(train)).equals(harus.substring(0, 1))) {
                        hasil[i] = hasil[i] + 1;
                    }
                    j++;
                }
                j = ((i + 1) * bagi);
                while (j < dat.numInstances()) {
                    Instance train = dat.instance(j);
                    String harus = train.toString();
                    if (!model.executeInstance(InstancesConverter.convert2(train)).equals(harus.substring(0, 1))) {
                        hasil[i] = hasil[i] + 1;
                    }
                    j++;
                }
            }

            j = 0;
            i++;
        }
//        System.out.println("Correctly classified instances   : " +rata(hasil)+ " instances    " +((double)rata(hasil)/(double)dat.numInstances()*100)+"%" );
//        System.out.println("Incorrectly classified instances : " +(dat.numInstances()-rata(hasil))+ " instances    " +((double)(dat.numInstances()-rata(hasil))/(double)dat.numInstances()*100)+"%" );
//        System.out.println("Root mean square error           : " +rms(accu(invers(hasil,dat.numInstances()),dat.numInstances())));
//        System.out.println("Number of Instances              : " +dat.numInstances() +" instances");
        String hasil1 = "Correctly classified instances   : " +rata(hasil)+ " instances    " +((double)rata(hasil)/(double)dat.numInstances()*100)+"%"+"\n";
        String hasil2 = "Incorrectly classified instances : " +(dat.numInstances()-rata(hasil))+ " instances    " +((double)(dat.numInstances()-rata(hasil))/(double)dat.numInstances()*100)+"%\n" ;
        String hasil3 = "Root mean square error           : " +rms(accu(invers(hasil,dat.numInstances()),dat.numInstances()))+"\n" ;
        String hasil4 = "Number of Instances              : " +dat.numInstances() +" instances\n";
        String shasil = hasil1+hasil2+hasil3+hasil4;
        return shasil;
    }
    public static int[] invers(int[] nums, int jum){
        for (int i = 0; i < nums.length; i++) {
            nums[i] = jum - nums[i];
        }
        return nums;
    }

     public static double rms(int[] nums) {
        double ms = 0;
        for (int i = 0; i < nums.length; i++) {
            ms += nums[i] * nums[i];
        }
        ms /= nums.length;
        return Math.sqrt(ms);
    }
    public static double rms(double[] nums){
        double ms = 0;
        for (int i = 0; i < nums.length; i++)
            ms += nums[i] * nums[i];
        ms /= nums.length;
        return Math.sqrt(ms);
    }
    public static int rata(int[] nums){
        int ms = 0;
        for (int i = 0; i < nums.length; i++) {
            ms = ms+ nums[i];
        }
        ms = ms / nums.length;
        return ms;
    }
    public static double[] accu(int[] nums , int jum){
        double[] acc = new double[nums.length];
        for (int i = 0; i < nums.length; i++) {
            acc[i] = ((double)nums[i]/(double)jum);
        }
        return acc;
    }






  public static void main(String[] args) {
    Pair<Fraction, Fraction> pro = Pair.makePair(new Fraction(1, 1), new Fraction(2, 3));
    ArrayList<HashMap<String, Pair<Fraction, Fraction>>> data = new ArrayList<HashMap<String, Pair<Fraction, Fraction>>>();
    NaiveBayesianModel nb = new NaiveBayesianModel(data, pro);
    try {
      nb.saveModel("tes.model");
      NaiveBayesianModel haha = new NaiveBayesianModel();
      System.out.println("kyaaa");
      haha.loadModel("tes.model");
      System.out.println(haha.toString());
    } catch (Exception e) {
      System.out.println(e);
    }



        String a = "C:\\AI\\specthearttrain.arff";
        FileReader file = new FileReader();
        int k = 5;
        Instances qwe = file.readFile(a);
        qwe.setClassIndex(0);
        NaiveBayesian asd = new NaiveBayesian();
        NaiveBayesianModel zxc = asd.NaiveBayesWEKA(qwe);

        String hasil1 = zxc.crossv(5,qwe);
        String hasil = zxc.crossval(5, qwe);
        System.out.println(hasil1);
        System.out.println(hasil);
//        System.out.println(rata(hasil));
//        double[] accu = accu(hasil,qwe.numInstances());
//        for(int q = 0; q < accu.length ; q++){
//            System.out.println(accu[q]);
//        }
//        System.out.println(rms(accu(hasil,qwe.numInstances())));




  }
}
